﻿using System;
using System.Collections.Generic;
using System.Reflection;

namespace ClassDataExport
{
    /// <summary>
    /// Kann für eine Klasse alle Werte für Standarddatentypen zurückgeben, z.B. string, int, double, DateTime, Boolean
    /// </summary>
    /// <typeparam name="T">T Muss das IStatistics Interface Implentiert haben, damit hier Informationen ausgegeben werden können</typeparam>
    public class ClassDataExport<T>
    {
        #region Member
        private List<PropertyValue<T>> _propertyValues;
        private SortedList<int, OutputProperties> _outputList;
        private List<T> _items;
        #endregion

        #region Properties
        /// <summary>
        /// Default Value: False; Gibt an ob versucht werden soll die Outputliste aus Attributen zusammenzubauen die in der übergebenen Klasse mittels 
        /// ClassDataExportAttribute gesetzt wurden.
        /// </summary>
        public Boolean IsAttrubiteOutputDataEnabled { get; set; }

        /// <summary>
        /// List die die Ausgabereihenfolge sowie die Propertynamen angibt die ausgegeben werden sollen, die kann hier extern gesetzt werden
        /// int -> stellt die Reihenfolge der Ausgabe dar
        /// </summary>
        public SortedList<int, OutputProperties> OutputList
        {
            get { return _outputList; }
            set { _outputList = value; }
        }

        /// <summary>
        /// Auflistung der Werte die in der Klasse gefunden wurde, die später ausgegeben werden sollen.
        /// </summary>
        private List<PropertyValue<T>> PropertyValues
        {
            get { return _propertyValues; }
            set { _propertyValues = value; }
        }

        /// <summary>
        /// Auflistung der Items derren Propertyvalues ausgegeben werden sollen.
        /// </summary>
        public List<T> Items
        {
            get { return _items; }
            set { _items = value; }
        }
        #endregion

        #region Konstruktor
        /// <summary>
        /// Initialisieren der ClassDataExport Class
        /// </summary>
        public ClassDataExport(): this(null, null)
        {
        }

        /// <summary>
        /// Initialisieren der ClassDataExport Class
        /// </summary>
        /// <param name="items">ItemsCollection übergeben von der die Propertyvalues ausgelesen werden sollen</param>
        public ClassDataExport(List<T> items)
            : this(items, null)
        {
        }

        /// <summary>
        /// Initialisieren der ClassDataExport Class
        /// </summary>
        /// <param name="items">ItemsCollection übergeben von der die Propertyvalues ausgelesen werden sollen</param>
        /// <param name="outputProperties">Liste der Namen der Properties die ausgegeben werden sollen int-> Reihenfolge, OutputProperties-> Classe mit den namen der Properties</param>
        public ClassDataExport(List<T> items, SortedList<int, OutputProperties> outputProperties)
        {
            PropertyValues = new List<PropertyValue<T>>();
            Items = items;
            OutputList = outputProperties;

            if (Items == null)
            {
                Items = new List<T>();
            }

            if (OutputList == null)
            {
                OutputList = new SortedList<int, OutputProperties>();
                //Wenn keine Outputliste übergeben wurde, wird zuerst versucht die Attribute auszulesen die in der Klasse gesetzt wurden auf den Properties.
                IsAttrubiteOutputDataEnabled = false;
            }
        }

        /// <summary>
        /// Initialisieren der ClassDataExport Class
        /// </summary>
        /// <param name="items">ItemsCollection übergeben von der die Propertyvalues ausgelesen werden sollen</param>
        /// <param name="isAttrubiteOutputDataEnabled">bt an ob versucht werden soll die Outputliste aus Attributen zusammenzubauen
        /// die in der übergebenen Klasse mittels ClassDataExportAttribute gesetzt wurden.</param>
        public ClassDataExport(List<T> items, bool isAttrubiteOutputDataEnabled)
            : this(items, new SortedList<int, OutputProperties>())
        {
            IsAttrubiteOutputDataEnabled = isAttrubiteOutputDataEnabled;
        }

        #endregion

        #region Functions/Subs
        /// <summary>
        /// Ausgabe der KlassenPropertyValues in dem gewünschten Format.
        /// </summary>
        public DataOutput<T> GetDataOutput()
        {
            //Laden der Klassendaten
            LoadCLassData();

            DataOutput<T> dataOutput = new DataOutput<T>(OutputList);
            dataOutput.PropertyValues = PropertyValues;

            return dataOutput;
        }

        /// <summary>
        /// Laden der Klassendaten. Durchgehen der ItemsListe und zusammenstellen der Propertiesinhalte in einer extra Liste.
        /// Abhängig vom datentyp
        /// </summary>
        private void LoadCLassData()
        {
            if (Items != null && Items.Count > 0)
            {
                //Zurücksetzten der Liste, damit diese wieder neu erstellt werden kann.
                PropertyValues = new List<PropertyValue<T>>();

                //Alle Items der Liste durchgehen mit dem Typ "T" der übergeben wurde
                foreach (T eItem in Items)
                {
                    //Laden der OutputListe
                    LoadOutputProperties(eItem);

                    //Initialisieren der PropertiesvAlue Collection für das aktuelle Item
                    PropertyValue<T> propertyValue = new PropertyValue<T>();
                    propertyValue.Item = eItem;

                    //Die Outputliste mit den Propertynamen durchgehen, die ausgegeben werden sollen für das aktuelle Objekt
                    foreach (KeyValuePair<int, OutputProperties> eKvp in OutputList)
                    {
                        //Laden des PropertyInfo Objektes und prüfen um welchen Datentyp es sich handelt.
                        PropertyInfo propertyInfo = eItem.GetType().GetProperty(eKvp.Value.PropertyName);
                        if (propertyInfo != null)
                        {
                            //Datentyp String
                            if (propertyInfo.PropertyType == typeof(string))
                            {
                                LoadPropertyInfoTypeString(propertyValue, propertyInfo, eKvp.Key);
                            }

                            //Datentyp String
                            if (propertyInfo.PropertyType == typeof(char))
                            {
                                LoadPropertyInfoTypeChar(propertyValue, propertyInfo, eKvp.Key);
                            }

                            //Datentyp DateTime
                            if (propertyInfo.PropertyType == typeof(DateTime))
                            {
                                LoadPropertyInfoTypeDateTime(propertyValue, propertyInfo, eKvp.Key, eKvp.Value);
                            }

                            //Datentyp Boolean
                            if (propertyInfo.PropertyType == typeof(bool))
                            {
                                LoadPropertyInfoTypeBoolean(propertyValue, propertyInfo, eKvp.Key);
                            }

                            //Datentyp Double
                            if (propertyInfo.PropertyType == typeof(double))
                            {
                                LoadPropertyInfoTypeDouble(propertyValue, propertyInfo, eKvp.Key, eKvp.Value);
                            }

                            //Datentyp Integer
                            if (propertyInfo.PropertyType == typeof(int))
                            {
                                LoadPropertyInfoTypeInteger(propertyValue, propertyInfo, eKvp.Key, eKvp.Value);

                            }

                            //Datentyp Decimal
                            if (propertyInfo.PropertyType == typeof(decimal))
                            {
                                LoadPropertyInfoTypeDecimal(propertyValue, propertyInfo, eKvp.Key, eKvp.Value);
                            }
                        }

                        //Den Datensatz mit dem PropertyValue der Auflistung hinzufügen
                    }

                    //Hinzufügen der PropertyValues zur Auflistung
                    PropertyValues.Add(propertyValue);
                }
            }
        }

        /// <summary>
        /// Für DateTime Properties zuständige Funktion, die das übergebenen Property der Auflistung hinzufügt und den Inhalt ausliest.
        /// </summary>
        /// <param name="propertyInfo">Das Property was ausgelesen werden soll</param>
        /// <param name="index">Index an welcher Stelle das Property ausgegeben werden soll</param>
        /// <param name="outputProperties">Die Propertiesklasse in der z.B. ein Formatstring gespeichert ist</param>
        /// <param name="propertyValue">Beinhaltet die Auflistung der ausgelesenen Properties für das in Ihm enthaltenene T - Objekt</param>
        private void LoadPropertyInfoTypeDateTime(PropertyValue<T> propertyValue, PropertyInfo propertyInfo, int index, OutputProperties outputProperties)
        {
            DateTime value = (DateTime)propertyInfo.GetValue(propertyValue.Item, null);
            string formatedValue = value.ToShortDateString();

            //Prüfen ob ein Formatstring hinterlegt wurde, wenn ja diesen laden
            if (!string.IsNullOrEmpty(outputProperties.FormatString))
            {
                formatedValue = value.ToString(outputProperties.FormatString);
            }

            //Erstellen der classData
            ClassData classData = new ClassData(propertyInfo, formatedValue, propertyInfo.Name, index);
            //Hinzufügen der ClassData zu den Werten des aktuell ausgewählten Items und dessen ValueCollection
            propertyValue.PropertyValues[index] = classData;
        }

        /// <summary>
        /// Für Double Properties zuständige Funktion, die das übergebenen Property der Auflistung hinzufügt und den Inhalt ausliest.
        /// </summary>
        /// <param name="propertyInfo">Das Property was ausgelesen werden soll</param>
        /// <param name="index">Index an welcher Stelle das Property ausgegeben werden soll</param>
        /// <param name="outputProperties">Die Propertiesklasse in der z.B. ein Formatstring gespeichert ist</param>
        /// <param name="propertyValue">Beinhaltet die Auflistung der ausgelesenen Properties für das in Ihm enthaltenene T - Objekt</param>
        private void LoadPropertyInfoTypeDouble(PropertyValue<T> propertyValue, PropertyInfo propertyInfo, int index, OutputProperties outputProperties)
        {
            double value = (double)propertyInfo.GetValue(propertyValue.Item, null);

            //Erstellen des classData Objektes
            ClassData classData = new ClassData(propertyInfo, value.ToString(outputProperties.FormatString), propertyInfo.Name, index);
            //Hinzufügen der ClassData zu den Werten des aktuell ausgewählten Items und dessen ValueCollection
            propertyValue.PropertyValues[index] = classData;
        }

        /// <summary>
        /// Für Double Properties zuständige Funktion, die das übergebenen Property der Auflistung hinzufügt und den Inhalt ausliest.
        /// </summary>
        /// <param name="propertyInfo">Das Property was ausgelesen werden soll</param>
        /// <param name="index">Index an welcher Stelle das Property ausgegeben werden soll</param>
        /// <param name="outputProperties">Die Propertiesklasse in der z.B. ein Formatstring gespeichert ist</param>
        /// <param name="propertyValue">Beinhaltet die Auflistung der ausgelesenen Properties für das in Ihm enthaltenene T - Objekt</param>
        private void LoadPropertyInfoTypeDecimal(PropertyValue<T> propertyValue, PropertyInfo propertyInfo, int index, OutputProperties outputProperties)
        {
            decimal value = (decimal)propertyInfo.GetValue(propertyValue.Item, null);

            //Erstellen des classData Objektes
            ClassData classData = new ClassData(propertyInfo, value.ToString(outputProperties.FormatString), propertyInfo.Name, index);
            //Hinzufügen der ClassData zu den Werten des aktuell ausgewählten Items und dessen ValueCollection
            propertyValue.PropertyValues[index] = classData;
        }

        /// <summary>
        /// Für Integer Properties zuständige Funktion, die das übergebenen Property der Auflistung hinzufügt und den Inhalt ausliest.
        /// </summary>
        /// <param name="propertyInfo">Das Property was ausgelesen werden soll</param>
        /// <param name="index">Index an welcher Stelle das Property ausgegeben werden soll</param>
        /// <param name="outputProperties">Die Propertiesklasse in der z.B. ein Formatstring gespeichert ist</param>
        /// <param name="propertyValue">Beinhaltet die Auflistung der ausgelesenen Properties für das in Ihm enthaltenene T - Objekt</param>
        private void LoadPropertyInfoTypeInteger(PropertyValue<T> propertyValue, PropertyInfo propertyInfo, int index, OutputProperties outputProperties)
        {
            int value = (int)propertyInfo.GetValue(propertyValue.Item, null);

            //Erstellen des classData Objektes
            ClassData classData = new ClassData(propertyInfo, value.ToString(outputProperties.FormatString), propertyInfo.Name, index);
            //Hinzufügen der ClassData zu den Werten des aktuell ausgewählten Items und dessen ValueCollection
            propertyValue.PropertyValues[index] = classData;
        }

        /// <summary>
        /// Für Boolean Properties zuständige Funktion, die das übergebenen Property der Auflistung hinzufügt und den Inhalt ausliest.
        /// </summary>
        /// <param name="propertyInfo">Das Property was ausgelesen werden soll</param>
        /// <param name="index">Index an welcher Stelle das Property ausgegeben werden soll</param>
        /// <param name="propertyValue">Beinhaltet die Auflistung der ausgelesenen Properties für das in Ihm enthaltenene T - Objekt</param>
        private void LoadPropertyInfoTypeBoolean(PropertyValue<T> propertyValue, PropertyInfo propertyInfo, int index)
        {
            Boolean value = (Boolean)propertyInfo.GetValue(propertyValue.Item, null);

            //Erstellen des classData Objektes
            ClassData classData = new ClassData(propertyInfo, value.ToString(), propertyInfo.Name, index);
            //Hinzufügen der ClassData zu den Werten des aktuell ausgewählten Items und dessen ValueCollection
            propertyValue.PropertyValues[index] = classData;
        }

        /// <summary>
        /// Für String Properties zuständige Funktion, die das übergebenen Property der Auflistung hinzufügt und den Inhalt ausliest.
        /// </summary>
        /// <param name="propertyInfo">Das Property was ausgelesen werden soll</param>
        /// <param name="index">Index an welcher Stelle das Property ausgegeben werden soll</param>
        /// <param name="propertyValue">Beinhaltet die Auflistung der ausgelesenen Properties für das in Ihm enthaltenene T - Objekt</param>
        private void LoadPropertyInfoTypeString(PropertyValue<T> propertyValue, PropertyInfo propertyInfo, int index)
        {
            string value = (string)propertyInfo.GetValue(propertyValue.Item, null);

            if (value == null)
            {
                value = string.Empty;
            }

            //Erstellen der classData
            ClassData classData = new ClassData(propertyInfo, value, propertyInfo.Name, index);
            //Hinzufügen der ClassData zu den Werten des aktuell ausgewählten Items und dessen ValueCollection
            propertyValue.PropertyValues[index] = classData;
        }

        /// <summary>
        /// Für String Properties zuständige Funktion, die das übergebenen Property der Auflistung hinzufügt und den Inhalt ausliest.
        /// </summary>
        /// <param name="propertyInfo">Das Property was ausgelesen werden soll</param>
        /// <param name="index">Index an welcher Stelle das Property ausgegeben werden soll</param>
        /// <param name="propertyValue">Beinhaltet die Auflistung der ausgelesenen Properties für das in Ihm enthaltenene T - Objekt</param>
        private void LoadPropertyInfoTypeChar(PropertyValue<T> propertyValue, PropertyInfo propertyInfo, int index)
        {
            char value = (char)propertyInfo.GetValue(propertyValue.Item, null);

            //Erstellen der classData
            ClassData classData = new ClassData(propertyInfo, value.ToString(), propertyInfo.Name, index);
            //Hinzufügen der ClassData zu den Werten des aktuell ausgewählten Items und dessen ValueCollection
            propertyValue.PropertyValues[index] = classData;
        }

        /// <summary>
        /// Laden der OutputProperties. Hier werden die Namen der Properties herausgesucht, bzw. geladen, die später in der Liste mit ausgeben werden sollen.
        /// </summary>
        /// <param name="item">Klasse T von der die OutputProperties herausgesucht werden sollen</param>
        private void LoadOutputProperties(T item)
        {
            //Wenn hier keine Werte hinterlegt wurden, alle Properies nach standarddatentypen durchgehen und die Liste damit füllen.
            if (OutputList != null && OutputList.Count == 0)
            {
                int count = 0;
                //Alle öffentlichen Member durchgehen und unserer Ausgabeliste hinzufügen
                foreach (PropertyInfo ePropertyInfo in item.GetType().GetProperties())
                {
                    //Auf Typen überprüfen die von uns unterstüzt werden und nur diese der Auflistung hinzufügen
                    if (ePropertyInfo.PropertyType == typeof(string) || ePropertyInfo.PropertyType == typeof(int) || ePropertyInfo.PropertyType == typeof(double) ||
                        ePropertyInfo.PropertyType == typeof(decimal) || ePropertyInfo.PropertyType == typeof(DateTime) || ePropertyInfo.PropertyType == typeof(Boolean) ||
                        ePropertyInfo.PropertyType == typeof(char))
                    {
                        if (IsAttrubiteOutputDataEnabled)
                        {
                            //Laden der OutputProperties aus den Attributen des Properties, was übergeben wird, muss für jedes Property einzeln durchgeführt werden!
                            LoadOutputPropertiesFromAttributes(ePropertyInfo);
                        }
                        else
                        {
                            //Es wurde keine Liste übergeben und IsAttrubiteOutputDataEnabled ist auch nicht eingeschalten, dann alle Properties ermitteln
                            OutputList.Add(count, new OutputProperties(ePropertyInfo.Name, string.Empty));
                            count++;
                            continue;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Erstellen der OutputProperties anhand des übergebenen PropertyInfo Objektes. Hier wird nach CustomAttributes gesucht vom Typ ClassDataExportAttribute.
        /// </summary>
        /// <param name="propertyInfo">PropertyInfo Objekt in dem nach CustomAttributes gesucht wird.</param>
        private void LoadOutputPropertiesFromAttributes(PropertyInfo propertyInfo)
        {
            //Aus den Attributen des übergebenen Properties das mit dem Typ ClassDataExportAttribute heraussuchen, hier kann nur 1 Attribut angeben werden, dieses auslesen
            ClassDataExportAttribute[] classDataExportAttribute = (ClassDataExportAttribute[]) propertyInfo.GetCustomAttributes(typeof(ClassDataExportAttribute), false);
            
            if (classDataExportAttribute != null && classDataExportAttribute.Length == 1)
            {
                //Hinzufügen des Attributes zur Liste, mit allen Informationen die in der Klasse angegeben wurden.
                OutputList[classDataExportAttribute[0].Position] = new OutputProperties(propertyInfo.Name, classDataExportAttribute[0].HeaderName, classDataExportAttribute[0].FormatString);
            }
        }
        #endregion
    }
}
